package cn.edu.csust.coursecard.service.impl;

import cn.edu.csust.coursecard.common.*;
import cn.edu.csust.coursecard.dao.StuInfoMapper;
import cn.edu.csust.coursecard.dao.dto.*;
import cn.edu.csust.coursecard.dao.entity.StuInfo;
import cn.edu.csust.coursecard.exception.BaseException;
import cn.edu.csust.coursecard.rabbitmq.MQSender;
import cn.edu.csust.coursecard.service.IParseHtmlService;
import cn.edu.csust.coursecard.service.StuInfoService;
import cn.edu.csust.coursecard.utils.*;
import cn.edu.csust.coursecard.utils.storage.FileUtils;
import cn.edu.csust.coursecard.utils.storage.StorageUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * @author zsw
 * @author 许大人
 * @version 2.0
 * @date 2019/11/18 18:55
 * @date 2022/08/10
 */
@Service
@CacheConfig(cacheNames = RedisKeyEnum.STU_INFO)
public class StuInfoServiceImpl implements StuInfoService {

    @Autowired
    StuInfoMapper stuInfoMapper;
    @Autowired
    RedisUtil redisUtil;
    @Autowired
    RedisTemplate<String, Object> redisTemplate;
    @Resource
    StorageUtils storageUtils;
    @Autowired
    TokenUtils tokenUtils;
    @Autowired
    MQSender mqSender;
    @Autowired
    SensitiveFilter sensitiveFilter;
    @Autowired
    IParseHtmlService parseHtmlService;

    @Resource
    private PageGetter pageGetter;

    private static final Logger LOGGER = LoggerFactory.getLogger(StuInfoServiceImpl.class);

    @Autowired
    @Qualifier("jwRequestExecutor")
    private ThreadPoolTaskExecutor pool;

//    private final ExecutorService executor = Executors.newFixedThreadPool(5);

    private int loopIndex = 0;
    //从教务系统获取code的url
    private static final String GET_CODE_URL = "http://xk.csust.edu.cn/Logon.do?method=logon&flag=sess";
    //教务系统登录的url
    private static final String LOGIN_URL = "http://xk.csust.edu.cn/Logon.do?method=logon";
    //教务系统学生主页的url
    private static final String XS_MAIN_JSP_URL = "http://xk.csust.edu.cn/jsxsd/framework/xsMain_new.jsp?t1=1";

    //JSESSIONID匹配的正则表达式
    private static final Pattern JW_JSESSIONID_Patten = Pattern.compile("^JSESSIONID=.*");
    private static final Pattern NUMBER_STRING = Pattern.compile("[0-9]*");
    private static final SimpleDateFormat daySdf = new SimpleDateFormat("yyyy-MM-dd");
    private static final SimpleDateFormat weekDaySdf = new SimpleDateFormat("EEEE");

    @Override
    public ReturnData loginThroughJW(String stuNum, String password, String version) {
        if (stuNum.isEmpty() || password.isEmpty()) {
            throw new BaseException(CodeEnum.PARAM_ERROR.getCode(), "登录参数错误！");
        }
        if (NUMBER_STRING.matcher(password).matches()){
            throw new BaseException(CodeEnum.PARAM_ERROR.getCode(), "请先到教务官网修改初始密码！");
        }
        String cookie = null;
        try {
            //从教务系统获取头部信息
            String header;
            Future<String> result = pool.submit(() -> getHeaderFromJW(stuNum, password));
            try {
                header = result.get(1500, TimeUnit.MILLISECONDS);
            } catch (Exception e) {
                throw new BaseException(CodeEnum.REQUEST_FAILED.getCode(), "教务请求失败");
            }
//            Guarded<String> guarded = new Guarded<>();
//            executor.execute(() -> {
//                guarded.setResult(getHeaderFromJW(stuNum, password));
//            });
//            //设置超时时间为1.5秒，超过1.5s不再等待教务请求结果，直接查询数据库
//            String header = guarded.getResult(1500);
//            if (header == null) {
//                throw new BaseException(CodeEnum.REQUEST_FAILED.getCode(), "教务请求失败");
//            }
            String[] split = header.split(";"); //JSESSIONID=83F3294B2F3361D1D91F0981ECDE5F49; Path=/jsxsd; HttpOnly
            for (String s : split) {
                if (JW_JSESSIONID_Patten.matcher(s).matches()) { //符合jsessionid开头
                    /*
                     * 登录成功后逻辑：
                     *  1.先按学号从数据库中查找改学生信息，判断是否存在
                     *  2.如果不存在，说明是第一次登录，将学生信息保存到数据库中
                     *  3.如果存在，更新登录时间，刷新用户数据
                     */
                    cookie = s;
                    StuInfo stuInfo = stuInfoMapper.selectByStudentNum(stuNum);
                    Integer id;
                    if (Objects.isNull(stuInfo)) {
                        StuInfo newStuInfo = new StuInfo(stuNum, MD5Utils.string2MD5(password), version);
                        stuInfoMapper.insertSelective(newStuInfo);
                        id = newStuInfo.getId();
                        //从教务获取教务信息并和默认信息一起更新至数据库（初始化）
                        refreshStuInfoFromJW(id, cookie, password, version);
                        //更新最近登录时间
                        mqSender.sendStuInfoMessage(new ObjectMapper().writeValueAsString(newStuInfo));
                    } else {
                        id = stuInfo.getId();
                        //stuInfo.setPassword(MD5Utils.string2MD5(password));
                        if (StringUtils.isEmpty(stuInfo.getMajor())) {
                            redisUtil.remove(RedisKeyEnum.USER_JWSTUINFO_PREFIX + id);
                            refreshStuInfoFromJW(id, cookie, password, version);
                        }
                        stuInfo.setAppVersion(version);
                        stuInfo.setPassword(MD5Utils.string2MD5(password));
                        mqSender.sendStuInfoMessage(new ObjectMapper().writeValueAsString(stuInfo));
                    }
                    String token = tokenUtils.generateToken(id, false);
                    Map<String, Object> data = new LinkedHashMap<>();
                    data.put("cookie", cookie);  //cookie -> JSESSIONID=83F3294B2F3361D1D91F0981ECDE5F49
                    data.put("token", token);  //83F3294B2F3361D1D91F0981ECDE5F49
                    redisUtil.set(RedisKeyEnum.USER_TOKEN_PREFIX + id, token, RedisKeyEnum.TOKEN_EXPIRE);
                    redisUtil.set(RedisKeyEnum.USER_STUNUM_PREFIX + id, stuNum, RedisKeyEnum.TOKEN_EXPIRE);
                    return ReturnData.success(data);
                }
            }
            return ReturnData.fail(CodeEnum.REQUEST_FAILED.getCode(), "账号或密码错误！");
        } catch (Exception e) {
            if (e instanceof BaseException) {
                LOGGER.error(((BaseException) e).getMsg());
            } else {
                LOGGER.error(e.getMessage());
            }
            return loginThroughDB(stuNum, password, version, cookie);
        }
    }

    private String getHeaderFromJW(String stuNum, String password) {

        Response response = null;
        Response updateCookieResponse = null;
        try {
            String[] jwCode = getJwCode();
            //2010AV81T974906Y80071f0Uyf1d21T5F%1%ir%8dlC63h34eg2n4g155123!
            String encoded = encodePsd(stuNum, password, jwCode[1]);
            OkHttpClient okHttpClient = new OkHttpClient.Builder().followRedirects(false).build();
            FormBody formBody = new FormBody.Builder()
                    .add("userAccount", stuNum)
                    .add("userPassword", password)
                    .add("encoded", encoded).build();  //加密之后的密码 2S0g012198A20481QX05i10D0Q331172157X5%d%g80%V5C6h56fe9nlg00F123!
            //Request{method=POST, url=http://xk.csust.edu.cn/Logon.do?method=logon, headers=[Cookie:JSESSIONID=D45529F57ADB0287E43350D531DD3EB7, Host:xk.csust.edu.cn, Origin:http://xk.csust.edu.cn, Referer:http://xk.csust.edu.cn/]}
            Request jwLoginRequest = new Request.Builder()
                    .header("Cookie", "JSESSIONID=" + jwCode[0])
                    .header("Host", "xk.csust.edu.cn")
                    .header("Origin", "http://xk.csust.edu.cn")
                    .header("Referer", "http://xk.csust.edu.cn/")
                    .url(LOGIN_URL) //http://xk.csust.edu.cn/Logon.do?method=logon
                    .post(formBody)
                    .build();
            response = okHttpClient.newCall(jwLoginRequest).execute();
            //Response{protocol=http/1.1, code=302, message=, url=http://xk.csust.edu.cn/Logon.do?method=logon}
            //updateCookieUrl： http://xk.csust.edu.cn/jsxsd/xk/LoginToXk?method=jwxt&ticqzket=c910ec98ba5756eeb9f660d4dc2d080f43c1c5abded67a5633d4897d77b9c6ffbbb8594cbcfcf26f371d4308b65a6647af52aa83ecbca08e2e6bed301946493a130570b925f97f396a5de375ca35438eba0f83ba2d745c6adbe6947d891b3bcb505b21504dc53d5eda9115e985e20f4c
            String updateCookieUrl = response.header("Location");

            if (updateCookieUrl == null) {
                return null;
            }
            //?????为什么要updateCookie
            OkHttpClient updateCookieClient = new OkHttpClient.Builder().followRedirects(false).build();
            Request updateCookieRequest = new Request.Builder()
                    .header("Cookie", jwCode[0]) //3671960E207E0379E35A75699C898405
                    .header("Referer", "http://xk.csust.edu.cn/")
                    .url(updateCookieUrl)
                    .build();
            updateCookieResponse = updateCookieClient.newCall(updateCookieRequest).execute();
            //JSESSIONID=83F3294B2F3361D1D91F0981ECDE5F49; Path=/jsxsd; HttpOnly
            return updateCookieResponse.header("Set-Cookie");
        } catch (Exception e) {
            return null;
        } finally {
            if (response != null) {
                response.close();
            }
            if (updateCookieResponse != null) {
                updateCookieResponse.close();
            }
        }
    }

    @Override
    public ReturnData getBasicData(String cookie) {
        Map<String, Object> data = new LinkedHashMap<>();
        String nowSemester; //2021-2022-2
        if (redisUtil.exist(RedisKeyEnum.NOW_SEMESTER_PREFIX)) {
            nowSemester = (String) redisUtil.get(RedisKeyEnum.NOW_SEMESTER_PREFIX);
        } else {
            nowSemester = getNowSemester(cookie);
            redisUtil.set(RedisKeyEnum.NOW_SEMESTER_PREFIX, nowSemester, (long) (60 * 60 * 24));
        }
        data.put("nowXueqi", nowSemester);
        data.put("nowDate", daySdf.format(new Date()));
        List<Integer> nowWeekDate = getNowWeekDate(cookie);
        data.put("nowWeek", nowWeekDate.get(0)); //当前第几周
        data.put("totalWeek", nowWeekDate.get(1)); //总共20周
        return ReturnData.success(data);
    }

    @Override
    public ReturnData getAllTermList(HttpServletRequest request, String cookie) {
        int userId = Integer.parseInt(request.getAttribute("userId").toString());
        String stuId = String.valueOf(redisUtil.get(RedisKeyEnum.USER_STUNUM_PREFIX + userId));
        return ReturnData.success(getAllSemester(stuId, cookie));
    }

    /**
     * 查看某一周的课表
     *
     * @param zc    看第几周的课表(必须在1-20)之间
     * @param xueqi 看哪个学期的课表
     */
    @Override
    public ReturnData getCourse(String cookie, String xueqi, Integer zc) {
        String htmlText;
        try {
            htmlText = pageGetter.getCoursePage(cookie, xueqi, zc);
        } catch (Exception e) {
            throw new BaseException(CodeEnum.SYSTEM_ERROR.getCode(), "教务系统无响应！");
        }
        WeekCourse[][] weekCourses = parseHtmlService.parseCourseHtml(htmlText);
        return ReturnData.success(weekCourses);
    }

    @Override
    public ReturnData getWeekCourse(String cookie, String weekTime) {
        String htmlText;
        try {
            htmlText = pageGetter.getWeekCoursePage(cookie, weekTime);
        } catch (Exception e) {
            throw new BaseException(CodeEnum.SYSTEM_ERROR.getCode(), "教务系统无响应！");
        }
        Course[][] courses = parseHtmlService.parseWeekCourseHtml(htmlText);
        redisUtil.setObject(RedisKeyEnum.WEEKCOURSE_PREFIX, RedisKeyEnum.WEEKCOURSE_EXPIRE);
        return ReturnData.success(courses);
    }


    /**
     * 获取考试安排
     *
     * @param xueqi 学期
     * @return {@link List< Exam >}
     */
    @Override
    public ReturnData getExamArrangement(String cookie, String xueqi) {
        String htmlText;
        try {
            htmlText = pageGetter.getExamPlansPage(cookie, xueqi);
        } catch (Exception e) {
            throw new BaseException(CodeEnum.SYSTEM_ERROR.getCode(), "教务系统无响应！");
        }
        List<Exam> examList = parseHtmlService.parseExamArrangementHtml(htmlText);
        return ReturnData.success(examList);
    }

    /**
     * 查询某一学期所有成绩列表
     *
     * @param xueqi 学期
     * @return {@link Score}
     */
    @Override
    public ReturnData getScoreList(String cookie, String xueqi) {
        String htmlText;
        try {
            htmlText = pageGetter.getScorePage(cookie, xueqi);
        } catch (Exception e) {
            throw new BaseException(CodeEnum.SYSTEM_ERROR.getCode(), "教务系统无响应！");
        }
        List<Score> scoreList = parseHtmlService.parseScoreHtml(htmlText);
        return ReturnData.success(scoreList);
    }


    /**
     * 查询某一项成绩详情
     *
     * @param pscjUrl 查询成绩返回的pscjUrl
     * @return {@link PscjInfo}
     */
    @Override
    public ReturnData getScoreDetail(String cookie, String pscjUrl) {
        String htmlText;
        try {
            htmlText = pageGetter.getScoreDetailPage(cookie, pscjUrl);
        } catch (Exception e) {
            throw new BaseException(CodeEnum.SYSTEM_ERROR.getCode(), "教务系统无响应！");
        }
        PscjInfo pscjInfo = parseHtmlService.parseScoreDetailHtml(htmlText);
        return ReturnData.success(pscjInfo);
    }


    /**
     * 获取周一和日期的对应
     */
    @Override
    public ReturnData getWeekDate(String cookie) {
        List<Integer> nowWeekDate = getNowWeekDate(cookie);
        Date date = new Date();
        String weekDay = weekDaySdf.format(date);
        Integer weekIdByStr = WeekDayEnum.getWeekIdByStr(weekDay);
        int sub = 1 - weekIdByStr;
        String dayOfWeekMon = CalendarUtil.getDateOfDesignDay(date, daySdf, sub);
        Date monDate;
        synchronized (daySdf) {
            try {
                monDate = daySdf.parse(dayOfWeekMon);
            } catch (ParseException e) {
                throw new BaseException(CodeEnum.SYSTEM_ERROR.getCode(), "系统错误，获取周数失败");
            }
        }
        List<WeekDay> weekDays = new ArrayList<>();
        int index = 0;

        for (int i = nowWeekDate.get(0); i >= 1; i--) {
            WeekDay weekDayTemp = new WeekDay();
            weekDayTemp.setWeekId(i);
            weekDayTemp.setWeekMonStr(CalendarUtil.getDateOfDesignDay(monDate, daySdf, index * -1 * 7));
            index++;
            weekDays.add(weekDayTemp);
        }
        index = 1;
        for (int i = nowWeekDate.get(0) + 1; i <= nowWeekDate.get(1); i++) {
            WeekDay weekDayTemp = new WeekDay();
            weekDayTemp.setWeekId(i);
            weekDayTemp.setWeekMonStr(CalendarUtil.getDateOfDesignDay(monDate, daySdf, index * 7));
            index++;
            weekDays.add(weekDayTemp);
        }
        Collections.sort(weekDays);
        return ReturnData.success(weekDays);
    }


    @Override
    public StuInfo refreshStuInfoFromJW(Integer id, String cookie, String password, String version) {
        StuInfo jwStuInfo = (StuInfo) redisUtil.getObject(RedisKeyEnum.USER_JWSTUINFO_PREFIX + id);
        if (!Objects.isNull(jwStuInfo)) {

            StuInfo redisDbStuInfo = (StuInfo) redisUtil.getObject(RedisKeyEnum.USER_DBSTUINFO_PREFIX + id);
            if (!Objects.isNull(redisDbStuInfo)) {
                return redisDbStuInfo;
            }
        }
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .header("Cookie", cookie)
                .header("Host", "xk.csust.edu.cn")
                .header("Referer", "http://xk.csust.edu.cn/jsxsd/framework/xsMain.jsp")
                .url(XS_MAIN_JSP_URL)
                .build();
        Response response = null;
        try {
            int count = 1;
            String htmlText = "";
            while (count <= 4) {
//                System.out.println("个人教务信息接口查询循环次数:" + count);
                response = client.newCall(request).execute();
                htmlText = Objects.requireNonNull(response.body()).string();
                Thread.sleep(500);
                if (StringUtils.hasText(htmlText)) {
                    break;
                }
                count++;
            }
            if (count == 5) {
                throw new BaseException(CodeEnum.SYSTEM_ERROR.getCode(), "教务系统无响应！");
            }
            response.close();
            Document doc = Jsoup.parse(htmlText);
            Element body = doc.body();
            Elements stuInfoDivs = body.getElementsByClass("middletopdwxxcont");
            if (StringUtils.isEmpty(stuInfoDivs)) {
//                System.out.println("个人教务信息页面内容：" + htmlText);
//                System.out.println("--------登录递归第" + (++loopIndex) + "次-------");
                ++loopIndex;
                if (loopIndex == 6) {
//                    System.out.println("个人信息获取失败！cookie无效");
                    throw new BaseException(CodeEnum.REQUEST_FAILED.getCode(), "个人信息获取失败！cookie无效");
                }
                StuInfo dbStuInfo = stuInfoMapper.selectByPrimaryKey(id);
                Thread.sleep(500);
                if (password == null) {
                    throw new BaseException(CodeEnum.REQUEST_FAILED.getCode(), "验证失败，请重新登录！");
                }
                if (version == null) {
                    version = dbStuInfo.getAppVersion();
                }
                loginThroughJW(dbStuInfo.getStuId(), MD5Utils.string2MD5(password), version);
            }
            jwStuInfo = new StuInfo();
            int index = 0;
            for (Element infoDiv : stuInfoDivs) {
                switch (index) {
                    case 1:
                        jwStuInfo.setName(infoDiv.text());
                        break;
                    case 2:
                        jwStuInfo.setStuId(infoDiv.text());
                        break;
                    case 3:
                        jwStuInfo.setCollege(infoDiv.text());
                        break;
                    case 4:
                        jwStuInfo.setMajor(infoDiv.text());
                        break;
                    case 5:
                        jwStuInfo.setClassName(infoDiv.text());
                        break;
                }
                index++;
            }
            if (!StringUtils.isEmpty(jwStuInfo.getMajor())) {
                redisUtil.setObject(RedisKeyEnum.USER_JWSTUINFO_PREFIX + id, jwStuInfo, RedisKeyEnum.TOKEN_EXPIRE);
            }
            StuInfo dbStuInfo = stuInfoMapper.selectByPrimaryKey(id);
            dbStuInfo.setJWInfo(jwStuInfo);
            dbStuInfo.setAvatar(getStudentAvatar(id, cookie));
            if (!StringUtils.isEmpty(dbStuInfo.getMajor())) {
                redisUtil.setObject(RedisKeyEnum.USER_DBSTUINFO_PREFIX + id, dbStuInfo, RedisKeyEnum.TOKEN_EXPIRE);
            }
            stuInfoMapper.updateByPrimaryKeySelective(dbStuInfo);
            return dbStuInfo;
        } catch (Exception e) {
            e.printStackTrace();
            if (response != null) {
                response.close();
            }
            throw new BaseException(CodeEnum.SYSTEM_ERROR.getCode(), "教务系统无响应！");
        }
    }


    @Override
    public StuInfo getStuInfoFromDB(Integer selfUserId, Integer userId, String cookie) {
        //id不相同说明是在查看别人的资料
        if (userId != null && !(selfUserId.equals(userId))) {
            StuInfo stuInfo = new StuInfo();
            StuInfo otherStuInfo = (StuInfo) redisUtil.getObject(RedisKeyEnum.USER_DBSTUINFO_PREFIX + userId);
            if (Objects.isNull(otherStuInfo)) {
                otherStuInfo = stuInfoMapper.selectByPrimaryKey(userId);
            }
            stuInfo.setUsername(otherStuInfo.getUsername());
            stuInfo.setAvatar(otherStuInfo.getAvatar());
            stuInfo.setSex(otherStuInfo.getSex());
            return stuInfo;
        }
        userId = selfUserId;
        StuInfo redisDbStuInfo = (StuInfo) redisUtil.getObject(RedisKeyEnum.USER_DBSTUINFO_PREFIX + userId);
        if (!Objects.isNull(redisDbStuInfo)) {
            return redisDbStuInfo;
        }
        StuInfo dbStuInfo = stuInfoMapper.selectByPrimaryKey(selfUserId);
        if (dbStuInfo == null) {
            throw new BaseException(CodeEnum.PARAM_ERROR, "用户id不存在");
        }
        if (cookie != null) {
            if ("".equals(dbStuInfo.getMajor()) || StringUtils.isEmpty(dbStuInfo.getMajor())) {
                dbStuInfo = refreshStuInfoFromJW(dbStuInfo.getId(), cookie, null, null);
            }
        }
        redisUtil.setObject(RedisKeyEnum.USER_DBSTUINFO_PREFIX + userId, dbStuInfo, RedisKeyEnum.TOKEN_EXPIRE);

        return dbStuInfo;
    }

    @Override
    public ReturnData setStuInfo(HttpServletRequest request, StuInfo stuInfo) {
        Integer id = (Integer) request.getAttribute("userId");
        stuInfo.setId(id);
        String username = stuInfo.getUsername();
        if ("".equals(username) || username == null) {
            throw new BaseException(CodeEnum.PARAM_ERROR.getCode(), "昵称不能为空！");
        }
        stuInfo.setUsername(sensitiveFilter.filter(stuInfo.getUsername()));
        stuInfo.setUpdateTime(new Date());
        stuInfoMapper.updateByPrimaryKeySelective(stuInfo);
        redisUtil.remove(RedisKeyEnum.USER_DBSTUINFO_PREFIX + id);
        return ReturnData.success();
    }


    @Override
    public String getStudentAvatar(Integer id, String cookie) {
        //先判断数据库里是否有，没有的话就从教务获取并刷新
        String imageUrl = stuInfoMapper.selectAvatar(id);
        if (StringUtils.isEmpty(imageUrl) || "".equals(imageUrl) || "null".equals(imageUrl)) {
            return restoreJwAvatar(id, cookie, 0);
        }
        return imageUrl;
    }

    @Override
    public ReturnData setStudentAvatar(HttpServletRequest request, MultipartFile file) {
        if (Objects.isNull(file)) {
            throw new BaseException(CodeEnum.PARAM_ERROR.getCode(), "参数错误");
        }
        String oldFileName = stuInfoMapper.selectAvatar((Integer) request.getAttribute("userId"));
        if (!StringUtils.isEmpty(oldFileName)) {
            try {
                int index = oldFileName.lastIndexOf("/");
                storageUtils.delete("head/" + oldFileName.substring(index + 1));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        String newFileName = "head/" + FileUtils.createFileName();
        storageUtils.upload(file, newFileName);
        StuInfo stuInfo = new StuInfo();
        Integer id = (Integer) request.getAttribute("userId");
        stuInfo.setId(id);
        //加入前缀
        stuInfo.setAvatar(storageUtils.getUrl() + newFileName);
        stuInfo.setUpdateTime(new Date());
        stuInfoMapper.updateByPrimaryKeySelective(stuInfo);
        redisUtil.remove(RedisKeyEnum.USER_DBSTUINFO_PREFIX + id);
        return ReturnData.success(storageUtils.getUrl() + newFileName);
    }

    @Override
    public String restoreJwAvatar(Integer id, String cookie, int flag) {
        //flag == 1 删掉旧的用于恢复默认头像
        if (flag == 1) {
            String oldFileName = stuInfoMapper.selectAvatar(id);
            if (!StringUtils.isEmpty(oldFileName)) {
                try {
                    int index = oldFileName.lastIndexOf("/");
                    storageUtils.delete("head/" + oldFileName.substring(index + 1));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        //flag != 1 用来从教务获取头像并上传
        byte[] bytes = getStudentAvatarFromJW(cookie);
        String newFileName = "head/" + FileUtils.createFileName();
        storageUtils.upload(bytes, newFileName);
        StuInfo stuInfo = new StuInfo();
        stuInfo.setId(id);
        stuInfo.setAvatar(storageUtils.getUrl() + newFileName);
        stuInfo.setUpdateTime(new Date());
        redisUtil.remove(RedisKeyEnum.USER_DBSTUINFO_PREFIX + id);
        stuInfoMapper.updateByPrimaryKeySelective(stuInfo);
        return stuInfo.getAvatar();
    }

    @Override
    public ReturnData getNoticeListFromJW(String cookie) {
        List<JWNotice> jwNoticeListFromRedis = (List<JWNotice>) redisUtil.getObject(RedisKeyEnum.JW_NOTICE);
        if (!Objects.isNull(jwNoticeListFromRedis) && jwNoticeListFromRedis.size() != 0) {
            return ReturnData.success(jwNoticeListFromRedis);
        }
        String htmlText;
        try {
            htmlText = pageGetter.getNoticeListPage(cookie);
        } catch (Exception e) {
            throw new BaseException(CodeEnum.SYSTEM_ERROR.getCode(), "教务系统无响应！");
        }
        List<JWNotice> jwNoticeList = parseHtmlService.parseNoticeHtml(htmlText);
        redisUtil.setObject(RedisKeyEnum.JW_NOTICE, jwNoticeList, RedisKeyEnum.Half_DAY_EXPIRE);
        return ReturnData.success(jwNoticeList);
    }

    @Override
    public ReturnData getNoticeDetail(String cookie, String ggid) {
        String htmlStringFromRedis = (String) redisUtil.get(RedisKeyEnum.JW_NOTICE_DETAIL + ggid);
        if (!StringUtils.isEmpty(htmlStringFromRedis)) {
            return ReturnData.success(htmlStringFromRedis);
        }
        try {
            String htmlString = pageGetter.getNoticeDetailPage(cookie, ggid);
            redisUtil.set(RedisKeyEnum.JW_NOTICE_DETAIL + ggid, htmlString);
            return ReturnData.success(htmlString);
        } catch (Exception e) {
            throw new BaseException(CodeEnum.SYSTEM_ERROR.getCode(), "教务系统无响应！");
        }
    }

    @Override
    public UserInfo getUserInfo(Integer userId) {
        StuInfo stuInfo = getStuInfoFromDB(userId, userId, null);
        return new UserInfo(userId, stuInfo.getAvatar(), stuInfo.getUsername());
    }

    @Override
    public void solveNewStudentMistake() {
        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy");
            String grade = simpleDateFormat.format(System.currentTimeMillis());
            System.out.println(grade);
            System.out.println(stuInfoMapper.solveNewStudentMistake(grade));
        }catch (Exception e){
            throw new BaseException(CodeEnum.SYSTEM_ERROR.getCode(), "解决头像问题失败！");
        }
    }

    /**
     * 从教务系统获取一个 JSESSIONID和code
     * <p>
     * 返回：数组[1] ：01AV8T7496Y07fUyfd1TF1ir8dl6334g24155#23132111312112322213
     * 数组[0] ：F6A7D4407CE7428587295F070C2CEF5A
     */
    private String[] getJwCode() throws IOException {
        OkHttpClient okHttpClient = new OkHttpClient();
        Request jwCodeRequest = new Request.Builder().url(GET_CODE_URL).build(); //http://xk.csust.edu.cn/Logon.do?method=logon&flag=sess
        Response response = okHttpClient.newCall(jwCodeRequest).execute();
        String header = response.header("Set-Cookie");
        String[] split = header.split(";");
        String[] result = new String[2];
        for (String s : split) {
            if (JW_JSESSIONID_Patten.matcher(s).matches()) {
                result[0] = s.split("=")[1];
            }
        }
        result[1] = response.body().string();
        response.body().close();
        return result;
    }

    private static String encodePsd(String username, String password, String dataStr) {
        String[] splitCode = dataStr.split("#");
        String scode = splitCode[0];
        String sxh = splitCode[1];
        String code = username + "%%%" + password;
        String encode = "";
        for (int i = 0; i < code.length(); i++) {
            if (i < 20) {
                int theIndex = Integer.parseInt(sxh.substring(i, i + 1));
                encode = encode + code.charAt(i) + scode.substring(0, theIndex);
                scode = scode.substring(theIndex);
            } else {
                encode = encode + code.substring(i);
                i = code.length();
            }
        }
        return encode;
    }

    private ReturnData loginThroughDB(String studentNum, String password, String version, String cookie) {
        StuInfo stuInfo = stuInfoMapper.selectByStudentNum(studentNum);
        if (Objects.isNull(stuInfo)) {
            throw new BaseException(CodeEnum.REQUEST_FAILED.getCode(), "学号密码错误或教务系统无响应");
        }
        if (MD5Utils.passwordIsTrue(password, stuInfo.getPassword())) {
            stuInfo.setAppVersion(version);
            try {
                mqSender.sendStuInfoMessage(new ObjectMapper().writeValueAsString(stuInfo));
            } catch (JsonProcessingException e) {
                throw new BaseException(CodeEnum.SYSTEM_ERROR.getCode(), "系统错误");
            }
            String token = tokenUtils.generateToken(stuInfo.getId(), false);
            Map<String, Object> data = new LinkedHashMap<>();
            data.put("cookie", "");
            data.put("token", token);
            redisUtil.set(RedisKeyEnum.USER_TOKEN_PREFIX + stuInfo.getId(), token, RedisKeyEnum.TOKEN_EXPIRE);
            return ReturnData.success(data);
        } else {
            throw new BaseException(CodeEnum.REQUEST_FAILED.getCode(), "学号或密码错误");
        }
    }

    /**
     * 获得现在是第几学期
     *
     * @return
     */
    private String getNowSemester(String cookie) {
        try {
            String htmlText = pageGetter.getSemesterPage(cookie);
            Document document = Jsoup.parse(htmlText);
            Element xnxq01id = document.getElementById("xnxq01id");
            Elements select = xnxq01id.select("[selected=selected]");
            return select.text();
        } catch (Exception e) {
            throw new BaseException(CodeEnum.SYSTEM_ERROR.getCode(), "教务系统无响应！");
        }
    }

    private List<String> getAllSemester(String studentNum, String cookie) {
        int rxYear = Integer.parseInt(studentNum.substring(0, 4));
        String nowXueqi = (String) redisUtil.get(RedisKeyEnum.NOW_SEMESTER_PREFIX);
        if (Objects.isNull(nowXueqi)) {
            nowXueqi = getNowSemester(cookie);
        }
        int nowYear = Integer.parseInt(nowXueqi.substring(0, 4));
        List<String> list = new LinkedList<>();
        if (isFirstSemester(nowXueqi)) {
            for (int i = rxYear; i <= nowYear; i++) {
                list.add(i + "-" + (i + 1) + "-1");
                list.add(i + "-" + (i + 1) + "-2");
            }
        } else {
            int i = rxYear;
            for (; i <= nowYear; i++) {
                list.add(i + "-" + (i + 1) + "-1");
                list.add(i + "-" + (i + 1) + "-2");
            }
            list.add(i + "-" + (i + 1) + "-1");
        }
        return list;
    }

    private boolean isFirstSemester(String nowXueqi) {
        try {
            String[] strings = nowXueqi.split("-");
            if ("1".equals(strings[2])) {
                //如果是第一学期
                return true;
            } else {
                return false;
                //如果是第二学期
            }
        } catch (Exception e) {
            throw new BaseException(CodeEnum.SYSTEM_ERROR.getCode(), "系统错误,缓存可能超时");
        }
    }

    /**
     * 获得现在是第几周和总周数
     * 0位置为 第几周
     * 1位置为 总周数
     *
     * @param cookie
     * @return
     */
    public List<Integer> getNowWeekDate(String cookie) {
        List<Integer> nowWeekDateFromRedis = (List<Integer>) redisUtil.getObject(RedisKeyEnum.NOW_WEEK_DATE);
        //System.out.println(Arrays.toString(nowWeekDateFromRedis));
        if (!Objects.isNull(nowWeekDateFromRedis) && nowWeekDateFromRedis.size() != 0) {
            return nowWeekDateFromRedis;
        }
        String htmlText;
        try {
            htmlText = pageGetter.getWeekDatePage(cookie);
        } catch (Exception e) {
            throw new BaseException(CodeEnum.SYSTEM_ERROR.getCode(), "教务系统无响应！");
        }
        List<Integer> nowWeekDate = parseHtmlService.parseWeekDateHtml(htmlText);
        //redisTemplate.boundSetOps(RedisKeyEnum.NOW_WEEK_DATE).add(nowWeekDate[0],nowWeekDate[1]);
        redisUtil.setObject(RedisKeyEnum.NOW_WEEK_DATE, nowWeekDate, RedisKeyEnum.ONE_DAY_EXPIRE);
        return nowWeekDate;
    }


    /**
     * 从教务系统获取学生照片
     */
    private byte[] getStudentAvatarFromJW(String cookie) {
        OkHttpClient okHttpClient = new OkHttpClient();
        Request jwXszpRequest = new Request.Builder()
                .header("Referer", "http://xk.csust.edu.cn/jsxsd/framework/xsMain_new.jsp?t1=1")
                .header("Cookie", cookie)
                .url("http://xk.csust.edu.cn/jsxsd/grxx/xszpLoad")
                .build();
        Response response = null;
        try {
            int count = 1;
            byte[] bytes = null;
            while (count <= 4) {
                response = okHttpClient.newCall(jwXszpRequest).execute();
                bytes = Objects.requireNonNull(response.body()).bytes();
                Thread.sleep(1000);
                if (bytes.length > 0) {
                    break;
                }
                count++;
            }
            response.close();
            return bytes;
        } catch (Exception e) {
            if (response != null) {
                response.close();
            }
            throw new BaseException(CodeEnum.SYSTEM_ERROR.getCode(), "教务系统无响应！");
        }
    }
}
